use crate::error::Error;
use crate::from_meta::FromMeta;
use syn::Attribute;
use syn::Result;

pub trait FromAttrs: Sized {
    type Wrapper;

    fn init() -> Self::Wrapper;

    fn visit_attr(value: &mut Self::Wrapper, attr: &Attribute, field: &str) -> Result<bool>;

    fn finish(value: Self::Wrapper, field: &str) -> Result<Self>;
}

impl<T: FromMeta> FromAttrs for T {
    type Wrapper = Option<T>;

    fn init() -> Self::Wrapper {
        None
    }

    fn visit_attr(value: &mut Self::Wrapper, attr: &Attribute, field: &str) -> Result<bool> {
        let result = T::from_meta(&attr.meta);
        match result {
            Ok(item) => {
                if value.is_some() {
                    return Err(Error::duplicate_field(field).into());
                }
                *value = Some(item);
                return Ok(true);
            }
            Err(e) => {
                if !e.to_string().starts_with("Unsupported shape") {
                    return Err(e);
                }
            }
        }
        Ok(false)
    }

    fn finish(value: Self::Wrapper, field: &str) -> Result<Self> {
        value.ok_or(Error::missing_field(field).into())
    }
}

impl<T: FromMeta> FromAttrs for Option<T> {
    type Wrapper = Option<T>;

    fn init() -> Self::Wrapper {
        None
    }

    fn visit_attr(value: &mut Self::Wrapper, attr: &Attribute, field: &str) -> Result<bool> {
        <T as FromAttrs>::visit_attr(value, attr, field)
    }

    fn finish(value: Self::Wrapper, _field: &str) -> Result<Self> {
        Ok(value)
    }
}

impl<T: FromMeta> FromAttrs for Vec<T> {
    type Wrapper = Vec<T>;

    fn init() -> Self::Wrapper {
        vec![]
    }

    fn visit_attr(value: &mut Self::Wrapper, attr: &Attribute, _field: &str) -> Result<bool> {
        let result = T::from_meta(&attr.meta);
        match result {
            Ok(item) => {
                value.push(item);
                return Ok(true);
            }
            Err(e) => {
                if !e.to_string().contains("Unsupported shape") {
                    return Err(e);
                }
            }
        }
        Ok(false)
    }

    fn finish(value: Self::Wrapper, _field: &str) -> Result<Self> {
        Ok(value)
    }
}
